/*
 * Adara WebCam TimeLapse capture software.
 * Copyright (C) 2012 AdaraProjectTeam
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package adara.core;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;

import com.lti.civil.CaptureDeviceInfo;
import com.lti.civil.CaptureException;
import com.lti.civil.CaptureStream;
import com.lti.civil.CaptureSystem;
import com.lti.civil.CaptureSystemFactory;
import com.lti.civil.DefaultCaptureSystemFactorySingleton;

/**
 * This class implements the core functionality of the whole application.
 * The class define the API for accessing the list of available device,
 * the method to get a device by ID and a factory method to create a process
 * which will capture the images from specified device.
 * The class implement the singleton pattern.
 * 
 * @author Andrei Ermicioi (aka erani) <andrei.ermicioi@gmail.com>
 *
 */
public class AdaraCore {
	
	private static final Logger logger = Logger.getLogger(AdaraCore.class);

	private static AdaraCore instance = null;
	
	private CaptureSystemFactory captureSystemFactory;
	private CaptureSystem captureSystem;
	
	private List<Device> devices;
	
	private AdaraCore() {
		if (logger.isTraceEnabled())
			logger.trace("Build up the Core");
		
		captureSystemFactory = DefaultCaptureSystemFactorySingleton.instance();
		
		try {
			captureSystem = captureSystemFactory.createCaptureSystem();
			captureSystem.init();
		} catch (CaptureException e) {
			throw new CoreRuntimeException("Could not init the core system", e);
		}
	}
	
	/**
	 * Return the stream object which is attached to provided device. 
	 * @param dev
	 * @return
	 * @throws CoreException if the stream could not be opened.
	 */
	private CaptureStream factoryStream(Device dev) throws CoreException {
		CaptureStream stream = null;
		
		synchronized (captureSystem) {
			try {
				stream = captureSystem
						.openCaptureDeviceStream(dev.getDeviceId());
			} catch (CaptureException e) {
				throw new CoreException("Can not get device stream", e);
			}
		}
		
		return stream;
	}
	
	/**
	 * Return a list of available devices.
	 * @return
	 * @throws CoreException if the list of devices could not be obtained.
	 */
	@SuppressWarnings("unchecked")
	public synchronized static List<Device> getDeviceList() throws CoreException {
		if (logger.isTraceEnabled())
			logger.trace("Listing devices");
		
		if (instance().devices != null) {
			return instance().devices;
		}
		
		if (logger.isTraceEnabled())
			logger.trace("Building devices list");
		
		List<Device> devices = new ArrayList<Device>();
		List<CaptureDeviceInfo> idevices = null;
		
		try {
			idevices = instance().captureSystem.getCaptureDeviceInfoList();
		} catch (CaptureException e) {
			throw new CoreException(e);
		}
		
		if (logger.isTraceEnabled())
			logger.trace("Found devices: " + idevices.size());
		
		int seq = 0;
		
		for (CaptureDeviceInfo info : idevices) {
			devices.add(new Device(seq++, info));
			
			if (logger.isTraceEnabled()) {
				logger.trace("Device ID: " + info.getDeviceID());
				logger.trace("Device desc: " + info.getDescription());
			}
		}
		
		instance().devices = devices;
		
		return instance().devices;
	}
	
	/**
	 * Return a device object base on generated sequence. The sequence is the 
	 * order number by which the device was found.
	 * @param seq
	 * @return
	 * @throws CoreException if the device could not be found or the list of
	 * devices could not be obtained.
	 */
	public static Device getDeviceBySequence(int seq) throws CoreException {
		if (logger.isTraceEnabled())
			logger.trace("Select device by sequence " + seq);
		
		for (Device device : getDeviceList()) {
			if (device.getSequence() == seq) {
				if (logger.isTraceEnabled()) {
					logger.trace("Selected device:");
					logger.trace("Device ID: " + device.getDeviceId());
					logger.trace("Device desc: " + device.getDeviceDescription());
				}
				
				return device;
			}
		}
		
		throw new CoreException("No device by given sequence");
	}
	
	/**
	 * Factory a instance of Process class base on provided device.
	 * The method contains a synchronized piece of code to allow only 
	 * synchronized access to core resource.
	 * @param dev
	 * @param timegap
	 * @param storage
	 * @return
	 * @throws CoreException if the device stream could not be opened.
	 */
	public synchronized static Process factoryProcess(
			Device dev, int timegap, File storage) throws CoreException {
		if (logger.isTraceEnabled())
			logger.trace("Factory new process ");
		
		return new Process(dev, timegap, storage, instance().factoryStream(dev));
	}
	
	public static AdaraCore instance() {
		if (instance == null) {
			synchronized (AdaraCore.class) {
				if (instance == null) {
					instance = new AdaraCore();
				}
			}
		}
		
		return instance;
	}
}
